#include "game_http.h"
#include "game_version.h"
#include "game_string.h"
#include "game_file.h"
#include <unistd.h>
#include <fcntl.h>
#include "game_ev.h"
#include <event2/thread.h>

using namespace CGame::Httpd;

const CGameHttpd::Methods_t CGameHttpd::GET   = 1;
const CGameHttpd::Methods_t CGameHttpd::POST  = 2;

BOOL CGameHttpd::init(int port)
{
  return init(g_CGameEV.getBase(),"0.0.0.0", port, CGAME_HTTPD_THREAD_NUM);
}

BOOL CGameHttpd::init(int port, int numWorkers)
{
  return init(g_CGameEV.getBase(),"0.0.0.0", port, numWorkers);
}

CGame::Httpd::Response* CGameHttpd::newResponse(){
  return new Response(this);
}

CGame::Httpd::Request* CGameHttpd::newRequest(HttpdRequest_t* request){
  return new Request(request);
}

BOOL CGameHttpd::init(event_base* evbase,const char *addr, int port, int numWorkers)
{
  do
  {
    _evbase = evbase;
    _addr.assign(addr);
    _port = port;

    int fd = socket();
    CC_BREAK_IF(0 > fd);
    CC_BREAK_IF(!listen(fd,addr,port));

    _numWorkers = numWorkers;
    _response = newResponse();
    _stop = FALSE;
    for (int i = 0; i < _numWorkers; i++)
    {
      // thread_t thread;
      // pthread_create(&thread,NULL,_thread_t<CGameThread,&CGameThread::run>,this);

      evthread_use_pthreads();

      HttpdWorker_t* ctx = new HttpdWorker_t();
      ctx->_master = this;
      ctx->_evbase = event_base_new();
      ctx->_evhttp = evhttp_new(ctx->_evbase);
      evhttp_accept_socket(ctx->_evhttp, fd);
      evhttp_set_timeout(ctx->_evhttp, 5);

      // 设置默认回调函数，如果对应路径找不到回调，则会使用默认回调
      // evhttp_set_gencb(_evhttp, callback_gen, NULL);
      evhttp_set_gencb(ctx->_evhttp, __httpd_evhttp_cb<CGameHttpd, &CGameHttpd::onProcess>, this);
      // //设置某一路径的回调函数，即请求的时候，若path="/this"，则会走callback_s这个回调函数
      // evhttp_set_cb(_evhttp, "/this", callback_s, NULL);

      _workersVec.push_back(ctx);
    }

    char v[64] = {0};
    __version(v);
    char vv[128] = {0};
    snprintf(vv, sizeof(vv), "%s/%s", getName(), v);
    _vstring.assign(vv);

    CGameLogger *logger = CGameLoggerFactory::getInstance()->getLogger(getName());

    logger->info("Init %s on port: %d,root: %s.\n", vv, _port, _root.c_str());

    return TRUE;
  } while (true);

  return FALSE;
}

BOOL CGameHttpd::init(const char *addr, int port)
{
  return init(g_CGameEV.getBase(),addr, port, CGAME_HTTPD_THREAD_NUM);
}

BOOL CGameHttpd::stop()
{
  _stop = TRUE;

  for (std::vector<HttpdWorker_t*>::iterator itr = _workersVec.begin(); itr != _workersVec.end(); itr++)
  {
    HttpdWorker_t* ctx = (*itr);
    pthread_join(ctx->_thread, NULL);
  }

  _workersVec.clear();

  return TRUE;
}

void CGameHttpd::run()
{

  _stop = FALSE;

  for (std::vector<HttpdWorker_t*>::iterator itr = _workersVec.begin(); itr != _workersVec.end(); itr++)
  {
    HttpdWorker_t* ctx = (*itr);
    thread_t thread;
    pthread_create(&thread, NULL, _httpd_thread_cb<CGameHttpd, &CGameHttpd::work>, ctx);

#if CGAME_TARGET_PLATFORM == CGAME_PLATFORM_LINUX
    pthread_setname_np(thread,"CGameHttpd work");
#elif CGAME_TARGET_PLATFORM == CGAME_PLATFORM_MAC
    pthread_setname_np("CGameHttpd work");
#endif


    ctx->_thread = thread;
  }
}

void CGameHttpd::work(HttpdWorker_t* ctx)
{
  do
  {
    event_base_loop(ctx->_evbase, EVLOOP_NONBLOCK);
    usleep(_sleep * 1000);
  } while (!_stop);
}



CGameHttpd::CGameHttpd(){
  _root = "/var/share/CGameHttpd";
  _index = "index.html";
}

CGameHttpd::CGameHttpd(const char* root){
  _root.assign(root);
  _index = "index.html";
}

const char* CGameHttpd::getName(){
  return CGAME_HTTPD_NAME;
}

CGameHttpd::CGameHttpd(const char* root,const char* index){
  _root.assign(root);
  _index.assign(index);
}

const char* CGameHttpd::getRoot() const{
  return _root.c_str();
}


const std::string& CGameHttpd::vstring() const{
  return _vstring;
}



BOOL CGameHttpd::tryHandle(Request *req)
{
  const char *path = req->getRequestURL();
  char sss[256];
  getMappingString(sss, sizeof(sss), req->getMethod(), path);
  HandlerMap::iterator itr = _handlerMap.find(sss);

  if (_handlerMap.end() == itr)
    return FALSE;

  Handler &handler = (itr->second);
  handler(req);
  return TRUE;
}

void CGameHttpd::getMappingString(char *dest, size_t dest_max, Request::Method m, const char *path)
{
  if (Request::Method::GET == m)
  {
    snprintf(dest, dest_max, "GET %s", path);
  }
  else if (Request::Method::POST == m)
  {
    snprintf(dest, dest_max, "POST %s", path);
  }
}

void CGameHttpd::get(const char *c, Handler handler)
{
  char sss[256];
  getMappingString(sss, sizeof(sss), Request::Method::GET, c);
  _handlerMap.insert(std::make_pair(sss, handler));
}

void CGameHttpd::post(const char *c, Handler handler)
{
  char sss[256];
  getMappingString(sss, sizeof(sss), Request::Method::POST, c);
  _handlerMap.insert(std::make_pair(sss, handler));
}

void CGameHttpd::addRequestMapping(const char* value,CGame::Httpd::Handler handler){
  addRequestMapping(0xFFFF,value,handler);
}

void CGameHttpd::addRequestMapping(Methods_t methods,const char* value,CGame::Httpd::Handler handler){
  if(0 < (methods & GET)){
    get(value,handler);
  }

  if(0 < (methods & POST)){
    post(value,handler);
  }
}

void CGameHttpd::onProcess(HttpdRequest_t *request)
{
  Request *req = newRequest(request);
  doProcess(req);
}

void CGameHttpd::doProcess(Request *req)
{

  const char *path = req->getRequestURL();

  do
  {
    if (tryHandle(req))
    {
      break;
    }

    // char dest[C_MAX_FILENAME] = {0};

    // sprintf(dest, "%s%s", _root.c_str(), path);
    std::string dest = __string_format("%s%s", _root.c_str(), path);
    if (__is_directory_exist(dest))
    {

      dest += _index;
    }

    if (__is_file_exist(dest))
    {
      int fd = -1;

      if (0 < (fd = open(dest.c_str(), O_RDONLY)))
      {
        std::string ext = __get_file_extension(dest);
        _response->stream(req, fd, ext.c_str());
      }
    }
    else
      error(req, HTTP_NOTFOUND);
  } while (false);

  delete req;
}

void CGameHttpd::error(Request *req, int code)
{
  char v[64];
  __version(v);
  if (HTTP_NOTFOUND == code)
  {
    _response->html(req, code, CGAME_HTTPD_HTML_TL, Status::NOTFOUND.message, Status::NOTFOUND.message, v);
  }
}
